/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package com.mindbox.mueblesalpes.negocio.compras;

import com.mindbox.mueblesalpes.negocio.entity.CategoriaValor;
import com.mindbox.mueblesalpes.negocio.entity.Cliente;
import com.mindbox.mueblesalpes.negocio.entity.Compra;
import com.mindbox.mueblesalpes.negocio.entity.FormaPago;
import com.mindbox.mueblesalpes.negocio.entity.Mueble;
import com.mindbox.mueblesalpes.negocio.entity.MuebleInventario;
import com.mindbox.mueblesalpes.negocio.entity.TarjetaCredito;
import com.mindbox.mueblesalpes.negocio.entity.Usuario;
import com.mindbox.mueblesalpes.negocio.entity.Venta;
import com.mindbox.mueblesalpes.negocio.exception.ErrorCommit;
import com.mindbox.mueblesalpes.negocio.exception.ErrorInitTransaction;
import com.mindbox.mueblesalpes.negocio.exception.MontoTarjetaInsuficiente;
import com.mindbox.mueblesalpes.negocio.exception.NoSePudoGrabarTarjetaCredito;
import com.mindbox.mueblesalpes.negocio.exception.TarjetaCreditoNoExiste;
import com.mindbox.mueblesalpes.negocio.exception.TiendaException;
import com.mindbox.mueblesalpes.negocio.persistence.PersistenciaLocal;
import com.mindbox.mueblesalpes.negocio.persistence.TarjetaCreditoLocal;
import com.mindbox.mueblesalpes.negocio.persistence.UtilidadesPersistencia;
import com.mindbox.mueblesalpes.negocio.producto.IProductoService;
import com.mindbox.mueblesalpes.negocio.utilidades.UtilidadesTarjetaCredito;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import static com.mindbox.mueblesalpes.negocio.utilidades.UtilidadesCompra.*;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.annotation.Resource;
import javax.ejb.EJB;
import javax.ejb.Stateful;
import javax.ejb.TransactionManagement;
import javax.ejb.TransactionManagementType;
import javax.sql.DataSource;
import javax.transaction.NotSupportedException;
import javax.transaction.Status;
import javax.transaction.SystemException;
import javax.transaction.UserTransaction;

/**
 *
 * @author jsanchez
 */
@Stateful
@TransactionManagement(TransactionManagementType.BEAN)
public class CarritoComprasBean implements CarritoComprasLocal {

    // Add business logic below. (Right-click in editor and choose
    // "Insert Code > Add Business Method" or "Web Service > Add Operation")
    /**
     * Mueble seleccionado actualmente.
     */
    private Mueble muebleSeleccionado;
    /**
     * Carrito de compras.
     */
    private List<Compra> carritoCompras;
    /**
     * Representa a la nuevaVenta que se va a realizar en el momento.
     */
    private Venta venta;
    /**
     * Categorias de valor de la compra
     */
    private List<CategoriaValor> categoriasValor;
    private double subtotal;
    private double valorTotal;
    private String descripcionVenta;

    @EJB
    private IProductoService producto;
    @EJB
    private PersistenciaLocal persistencia;
    @EJB
    private TarjetaCreditoLocal tarjetaCreditoPersistencia;

    @Resource
    UserTransaction userTransaction;

    @Resource(mappedName = "jdbc/tiendaMindboxTC")
    private DataSource dataSource;

    /**
     * Inicializa el carrito de compras
     */
    public CarritoComprasBean() {
    }

/**
     * Inicia la transacción de una Transacción de BEAN
     * @param ut
     */
    public void initTransaction() throws ErrorInitTransaction {
        try {
            userTransaction.begin();
        } catch (NotSupportedException ex) {
            throw new ErrorInitTransaction(ex);
        } catch (SystemException ex) {
            throw new ErrorInitTransaction(ex);
        }
    }

    /**
     * Realizar commit de la transacción de una Transacción de BEAN
     * @param ut
     * @exception ErrorCommit
     */
    public void commitTransaction() throws ErrorCommit {
        try {
            if (userTransaction.getStatus() != Status.STATUS_ROLLEDBACK) {
                userTransaction.commit();
            }
        } catch (Exception ex) {
            try {
                userTransaction.rollback();
            } catch (Exception e) {
                throw new ErrorCommit(e);
            }
            throw new ErrorCommit(ex);
        }
    }

    /**
     * Realiza el rollback de la transacción de una Transacción de BEAN
     * @param ut
     */
    public void rollBackTransaction() {
        try {
            userTransaction.rollback();
        } catch (Exception e) {
            Logger.getLogger(CarritoComprasBean.class.getName()).log(Level.SEVERE, null, e);
        }
    }

    /**
     * Se ejecuta despues de contruir este objeto.
     */
    @PostConstruct
    public void init() {
        this.carritoCompras = new ArrayList<Compra>();
        this.categoriasValor = new ArrayList<CategoriaValor>();
        this.setValorTotal(0.0);
        this.setSubtotal(0.0);
        this.setMuebleSeleccionado(null);
        this.setVenta(null);
        this.setDescripcionVenta(null);
    }

    @Override
    public Mueble getMuebleSeleccionado() {
        return muebleSeleccionado;
    }

    @Override
    public void setMuebleSeleccionado(Mueble muebleSeleccionado) {
        this.muebleSeleccionado = muebleSeleccionado;
    }

    @Override
    public List<Compra> getCarritoCompras() {
        return carritoCompras;
    }

    @Override
    public void setCarritoCompras(List<Compra> carritoCompras) {
        this.carritoCompras = carritoCompras;
    }

    @Override
    public Venta getVenta() {
        return this.venta;
    }

    @Override
    public void setVenta(Venta venta) {
        this.venta = venta;
    }

    @Override
    public void setCategoriasValor(List<CategoriaValor> categoriasValor) {
        this.categoriasValor = categoriasValor;
    }

    @Override
    public List<CategoriaValor> getCategoriasValor() {
        return this.categoriasValor;
    }

    @Override
    public double getSubtotal() {
        return subtotal;
    }

    @Override
    public void setSubtotal(double subtotal) {
        this.subtotal = subtotal;
    }

    @Override
    public double getValorTotal() {
        return valorTotal;
    }

    @Override
    public void setValorTotal(double valorTotal) {
        this.valorTotal = valorTotal;
    }

    @Override
    public String getDescripcionVenta() {
        return descripcionVenta;
    }

    @Override
    public void setDescripcionVenta(String descripcionVenta) {
        this.descripcionVenta = descripcionVenta;
    }

    @Override
    public void validarAgregarMuebleSeleccionado() {
        // Se verifica si se debe agregar un producto al carrito
        if (this.getMuebleSeleccionado() != null) {

            Compra compraActual = null;
            for (int i = 0; i < this.getCarritoCompras().size() && compraActual == null; i++) {
                if (this.getCarritoCompras().get(i).getMueble().equals(this.muebleSeleccionado)) {
                    compraActual = this.getCarritoCompras().get(i);
                }
            }

            if (compraActual == null) {
                //Se debe agregar al carrito.
                this.getCarritoCompras().add(new Compra(this.getMuebleSeleccionado(), 1,
                        this.producto.consultarMuebleInventarioPorMueble(this.getMuebleSeleccionado()).getPrecio()));
            } else {
                // Se debe actualizar la catnidad de la compra
                compraActual.setCantidad(compraActual.getCantidad() + 1);
            }
            // Se coloca el mueble seleccionado en null para no volverlo a adicionar
            setMuebleSeleccionado(null);
            // Se debe calcular todos los valores:
            this.setSubtotal(calcularSubtotalCompra(this.getCarritoCompras()));
            this.setCategoriasValor(calcularValorCategoriasCompra(this.getSubtotal()));
            this.setValorTotal(calcularValorTotalCompra(this.getCategoriasValor()));
        }
    }

    @Override
    public void realizarVenta(Cliente cliente, Usuario usuario) {
        try {
            initTransaction();
            Venta nuevaVenta = new Venta();

            for (CategoriaValor cv : this.getCategoriasValor()) {
                cv.setVenta(nuevaVenta);
            }
            nuevaVenta.setCategoriasValor(this.getCategoriasValor());

            for (Compra c : this.getCarritoCompras()) {
                c.setVenta(nuevaVenta);
            }
            nuevaVenta.setCompras(this.getCarritoCompras());

            nuevaVenta.setCliente(cliente);
            nuevaVenta.setUsuario(usuario);
            nuevaVenta.setValorTotal(this.getValorTotal());

            persistencia.create(nuevaVenta);
            nuevaVenta.setReferencia(nuevaVenta.getIdVenta() + "");
            persistencia.update(nuevaVenta);

            commitTransaction();

            this.setVenta((Venta) nuevaVenta.clone());
            this.setDescripcionVenta(calcularDescripcionVenta(this.getVenta()));
        } catch (Exception ex) {
            rollBackTransaction();
            Logger.getLogger(CarritoComprasBean.class.getName()).log(Level.SEVERE, null, ex);
            throw new TiendaException("Error al crear la venta:" + ex.getMessage());
        }
    }

    @Override
    public void realizarPago() throws TarjetaCreditoNoExiste, NoSePudoGrabarTarjetaCredito, MontoTarjetaInsuficiente {
        try {
            initTransaction();
            afectarBDMuebles();
            if(this.getVenta().getEntidad().getFormaPago().equals(FormaPago.TARJETA_CREDITO_ALPES))
                afectarBDTarjetas(this.getVenta().getCliente(), this.getValorTotal());
            commitTransaction();
            enviarCorreo("mindbox09@gmail.com", "Compra de Productos", "Su compra ha sido exitosa:" + getDescripcionVenta());
        } catch (TarjetaCreditoNoExiste ex) {
            this.getVenta().setVentaPagada(false);
            rollBackTransaction();
            Logger.getLogger(CarritoComprasBean.class.getName()).log(Level.SEVERE, null, ex);
            throw ex;
        } catch (NoSePudoGrabarTarjetaCredito ex) {
            this.getVenta().setVentaPagada(false);
            rollBackTransaction();
            Logger.getLogger(CarritoComprasBean.class.getName()).log(Level.SEVERE, null, ex);
            throw ex;
        } catch (MontoTarjetaInsuficiente ex) {
            this.getVenta().setVentaPagada(false);
            rollBackTransaction();
            Logger.getLogger(CarritoComprasBean.class.getName()).log(Level.SEVERE, null, ex);
            throw ex;
        } catch(TiendaException te){
            this.getVenta().setVentaPagada(false);
            rollBackTransaction();
            Logger.getLogger(CarritoComprasBean.class.getName()).log(Level.SEVERE, null, te);
            throw te;
        } catch (Exception ex) {
            this.getVenta().setVentaPagada(false);
            rollBackTransaction();
            Logger.getLogger(CarritoComprasBean.class.getName()).log(Level.SEVERE, null, ex);
            throw new TiendaException("Error al realizar el pago:" + ex.getMessage());
        }
    }
    
    /**
     * Afecta la BD de Tarjetas de Crédito.
     * @param cliente
     */
    private void afectarBDTarjetas(Cliente cliente, double valorTotal) throws SQLException, TarjetaCreditoNoExiste, NoSePudoGrabarTarjetaCredito, MontoTarjetaInsuficiente{
        TarjetaCredito tc2 = sumarRestarMontoTarjeta(cliente, valorTotal * -1);
        if(tc2.getMontoTarjeta() < 0 )
            throw new MontoTarjetaInsuficiente(tc2);
    }

    /**
     * Suma o resta el monto de la tarjeta de crédito.
     * @param tc
     * @param monto
     */
    private TarjetaCredito sumarRestarMontoTarjeta(Cliente cliente, double monto) throws SQLException, NoSePudoGrabarTarjetaCredito, TarjetaCreditoNoExiste{
        Connection conexion = null;
        PreparedStatement ps = null;
        TarjetaCredito tc;

        conexion = dataSource.getConnection();
        conexion.setAutoCommit(false);
        
        //Se conuslta la tarjeta de credito dado el cliente
        String sql =
                "SELECT id, idcliente, montotarjeta from tarjeta_credito " +
                "WHERE idcliente = ?";
        ps = conexion.prepareStatement(sql);
        ps.setLong(1, cliente.getIdCliente().longValue());
        ResultSet rs = ps.executeQuery();
        if(!rs.next())
            throw new TiendaException("El cliente NO tiene tarjeta de crédito de mueble los Alpes");
        else
            tc = UtilidadesTarjetaCredito.construirTarjetaCreditoFromRS(rs);

        //Se actualiza el monto de la tarjeta de credito
        sql = "UPDATE TARJETA_CREDITO SET MONTOTARJETA = MONTOTARJETA + ? " +
              "WHERE id = ? ";
        ps = conexion.prepareStatement(sql);
        ps.setDouble(1, monto);
        ps.setLong(2, tc.getId().longValue());
        if (ps.executeUpdate() != 1)
            throw new NoSePudoGrabarTarjetaCredito(tc);

        //Se vuelve a consultar la tarjeta de credito
        sql = "SELECT id, idcliente, montotarjeta from tarjeta_credito " +
              "WHERE id = ?";
        ps = conexion.prepareStatement(sql);
        ps.setLong(1, tc.getId().longValue());
        rs = ps.executeQuery();
        if(!rs.next())
            throw new TarjetaCreditoNoExiste(tc);
        else
            tc = UtilidadesTarjetaCredito.construirTarjetaCreditoFromRS(rs);
        return tc;
    }

    /**
     * Afecta la BD de Muebles, disminuyendo del inventario y actualiza la venta
     */
    private void afectarBDMuebles(){
        this.disminuirInventario(this.getVenta().getCompras());
        this.getVenta().setVentaPagada(true);
        persistencia.update(this.getVenta());
    }

    /**
     * Disminuye del inventario las compras
     * @param compras
     */
    private void disminuirInventario(List<Compra> compras){
        for (Compra compra : compras) {
            MuebleInventario inventario = producto.consultarMuebleInventarioPorMueble(compra.getMueble());
            inventario.setCantidad(inventario.getCantidad() - compra.getCantidad());
            persistencia.update(inventario);
        }
    }

    @Override
    @PreDestroy
    public void cancelarPago() {
        try {
            if (this.getVenta() != null) {
                if (!this.venta.isVentaPagada()) {
                    initTransaction();
                    this.persistencia.delete(this.getVenta());
                    commitTransaction();
                } else {
                    throw new TiendaException("La venta ya fue pagada.");
                }
            }
        } catch (Exception ex) {
            rollBackTransaction();
            Logger.getLogger(CarritoComprasBean.class.getName()).log(Level.SEVERE, null, ex);
            throw new TiendaException("Error al cancelar el pago:" + ex.getMessage());
        } finally {
            terminarTransaccion();
        }
    }

    @Override
    public void terminarTransaccion() {
        this.init();
    }
}